namespace CcNetTeamBuildTask
{
    #region using directives
    using System;
    using System.Collections;
    using System.Diagnostics;
    using System.IO;
    using System.Text;

    using Exortech.NetReflector;
    using ThoughtWorks.CruiseControl.Core;
    using ThoughtWorks.CruiseControl.Core.Util;
    using ThoughtWorks.CruiseControl.Core.Tasks;
    using ThoughtWorks.CruiseControl.Remote;
    using CcnetProject = ThoughtWorks.CruiseControl.Core.Project;

    using Microsoft.TeamFoundation.Build.Common;
    //using MsBuildProject = Microsoft.TeamFoundation.Build.Common.Project;
    //using Proxy = Microsoft.TeamFoundation.Build.Proxy;
    //using Microsoft.TeamFoundation.VersionControl.Client;
    //using Microsoft.TeamFoundation.VersionControl.Common;
    //using Microsoft.TeamFoundation.Client;
    #endregion

    [ReflectorType("teambuild")]
    public class TeamBuildTask : ITask
    {
        private BuildConstants.BuildStatusIconID _teamBuildStatus;
        private IList _msBuildParameters;

        private const string _teamBuildLogFile = "BuildLog.txt";
        private const string _teamBuildErrorsWarningLog = "ErrorsWarningsLog.txt";
        private const string _msBuildXmlLogFile = "msbuild-output.xml";
        private const string coverageConvertTool = "CoverageConvert.exe";
        private const string coverageFilename = "data.coverage";
        private const string coverageXmlFilename = "coverage-results.xml";


        #region Constructor
        public TeamBuildTask()
        {
        }
        #endregion


        #region Configuration Properties
        /// <summary>
        /// The full URL of the TFS Server.
        /// </summary>
        [ReflectorProperty("tfsServerUri", Required = true)]
        public string TfsServerUri;

        /// <summary>
        /// The Team Build Server to run the build on.
        /// </summary>
        [ReflectorProperty("teamBuildServer", Required = true)]
        public string TeamBuildServer;

        /// <summary>
        /// The TFS Team Project that contains the Team Build Type to be run.
        /// </summary>
        [ReflectorProperty("teamProject", Required = true)]
        public string TeamProject;

        /// <summary>
        /// The Team Build Type to be run.
        /// </summary>
        [ReflectorProperty("teamBuildType", Required = true)]
        public string TeamBuildType;

        /// <summary>
        /// The TFS workspace to be used when polling for changes.
        /// </summary>
        [ReflectorProperty("workspace", Required = true)]
        public string Workspace;

        /// <summary>
        /// A collection of MSBuild parameters to be written to the TFSBuild.rsp file prior to starting the Team Build Type.
        /// </summary>
        [ReflectorCollection("msBuildParameters", InstanceType = typeof(ArrayList), Required = false)]
        public IList MsBuildParameters
        {
            get
            {
                if (_msBuildParameters == null)
                {
                    _msBuildParameters = new ArrayList();
                }
                return _msBuildParameters;
            }
            set
            {
                _msBuildParameters = value;
            }
        }

        /// <summary>
        /// Defines an MSBuild property to added to the TFSBuild.rsp file using the format 
        /// </summary>
        [ReflectorProperty("msBuildParameter", Required = false)]
        public string MsBuildParameter;

        /// <summary>
        /// If true, CruiseControl will attempt to gather MSTest results from the Team Build.
        /// </summary>
        [ReflectorProperty("useMsTest", Required = false)]
        public bool UseMsTest;

        /// <summary>
        /// If true, CruiseControl will attempt to Visual Studio Code Coverage result from Team Build and convert them an XML format.
        /// </summary>
        [ReflectorProperty("useMsTestCodeCoverage", Required = false)]
        public bool UseMsTestCodeCoverage;

        /// <summary>
        /// Use this to specify the location of TF.EXE when it has been installed to a non-default location.
        /// </summary>
        [ReflectorProperty("pathToTfExe", Required = false)]
        public string PathToTfExe;

        /// <summary>
        /// If true, Team Build will be automatically configured to use the CruiseControl MSBuild XML logger using a fixed filename.
        /// </summary>
        [ReflectorProperty("useXmlLogger", Required = false)]
        public bool UseXmlLogger;

        /// <summary>
        /// If true, the task will set the CCnet build label to be the same as the TFS build number. 
        /// </summary>
        [ReflectorProperty("useTfsBuildNumberAsLabel", Required = false)]
        public bool UseTfsBuildNumberAsLabel;
        #endregion


        #region ITask Members
        /// <summary>
        /// The entrypoint used by CCNet.
        /// </summary>
        public void Run(IIntegrationResult result)
        {
            try
            {
                TeamBuildManager teamBuildMgr = new TeamBuildManager();
                teamBuildMgr.TeamProject = TeamProject;
                teamBuildMgr.TeamBuildType = TeamBuildType;
                teamBuildMgr.TeamBuildServer = TeamBuildServer;
                teamBuildMgr.Workspace = Workspace;
                teamBuildMgr.TfsServerUri = TfsServerUri;

                // Override the default path to TF.EXE, if the option has been specified
                if (PathToTfExe != null)
                {
                    teamBuildMgr.PathToTfExe = PathToTfExe;
                }

                // Setup the appropriate MSBuild parameter if XML logging has been specified
                if (UseXmlLogger)
                {
                    _msBuildParameters.Add(string.Format("/logger:ThoughtWorks.CruiseControl.MsBuild.XmlLogger,ThoughtWorks.CruiseControl.MsBuild.dll;{0}", _msBuildXmlLogFile));
                }

                // If any custom MSBuild commandline parameters have been supplied then
                // call the method that writes these the TfsBuild.rsp file
                if (_msBuildParameters.Count > 0)
                {
                    Log.Info("Updating " + teamBuildMgr.TeamBuildRspFilename + " with custom MSBuild properties");
                    teamBuildMgr.MsBuildParameters = _msBuildParameters;
                    teamBuildMgr.UpdateTfsBuildRspFile();
                }

                _teamBuildStatus = teamBuildMgr.PerformTeamBuild();


                // If requested, use the TFS build number as the CCNet build label
                if (UseTfsBuildNumberAsLabel)
                {
                    Log.Info(string.Format("Using TFS build number as build label: {0}", teamBuildMgr.TfsBuildNumber));
                    result.Label = teamBuildMgr.TfsBuildNumber;
                }

                // Setup the return status based on the result back from TeamBuild
                string res = string.Empty;
                switch (_teamBuildStatus)
                {
                    case BuildConstants.BuildStatusIconID.BuildFailed:
                        res = "FAILED";
                        result.Status = IntegrationStatus.Failure;
                        break;
                    case BuildConstants.BuildStatusIconID.BuildSucceeded:
                        res = "SUCCEEDED";
                        result.Status = IntegrationStatus.Success;
                        break;
                    case BuildConstants.BuildStatusIconID.BuildStopped:
                        res = "STOPPED";
                        result.Status = IntegrationStatus.Unknown;
                        break;
                    default:
                        res = "UNKNOWN";
                        result.Status = IntegrationStatus.Unknown;
                        break;
                }

                // Return a basic status message
                result.AddTaskResult(string.Format("Result of TeamBuild '{0}' in {1} on {2}: {3}",
                                                        this.TeamBuildType,
                                                        this.TeamProject,
                                                        this.TeamBuildServer,
                                                        res));
                

                // If selected, add the XML log file to the CCNet build log
                if (UseXmlLogger)
                {
                    // TODO: This currently has to be done via a manual <Merge><File> task in CCNet, as we don't yet
                    //       have a graceful of way knowing exactly where the XML log file will be - as by default TeamBuild
                    //       will not copy it to the Drops Location.
                    //       However, if you tweak TeamBuild to do this (using the AfterDrop target for instance), then the
                    //       following will work and the manual <Merge><File> task is not required.
                    if (File.Exists(Path.Combine(teamBuildMgr.BuildDropPath, _msBuildXmlLogFile)))
                    {
                        Log.Info("Adding TeamBuild XML log file to build result");
                        result.AddTaskResult(new FileTaskResult(Path.Combine(teamBuildMgr.BuildDropPath, _msBuildXmlLogFile)));
                    }
                    else
                    {
                        Log.Warning(string.Format("TeamBuild XML log file not found in Drops location: {0}", Path.Combine(teamBuildMgr.BuildDropPath, _msBuildXmlLogFile)));
                    }
                }
                else
                {
                    // Otherwise just add the normal text log file from the Drops Location
                    if (File.Exists(Path.Combine(teamBuildMgr.BuildDropPath, _teamBuildLogFile)))
                    {
                        Log.Info("Adding TeamBuild log file to build result");
                        result.AddTaskResult(new FileTaskResult(Path.Combine(teamBuildMgr.BuildDropPath, _teamBuildLogFile)));
                    }
                    else
                    {
                        Log.Warning(string.Format("TeamBuild log file not found: {0}", Path.Combine(teamBuildMgr.BuildDropPath, _teamBuildLogFile)));
                    }
                }


                // If we are using MSTest then add the test results file so they can merged
                if ( UseMsTest )
                {
                    if (Directory.Exists(teamBuildMgr.MsTestResultsPath))
                    {
                        Log.Info("Adding MSTest result file(s) to build result");
                        foreach (string resultsFile in Directory.GetFiles(teamBuildMgr.MsTestResultsPath, "*.trx"))
                        {
                            result.AddTaskResult(new FileTaskResult(resultsFile));

                            if (UseMsTestCodeCoverage)
                            {
                                string coveragePath = Path.Combine(Path.GetDirectoryName(resultsFile), Path.GetFileNameWithoutExtension(resultsFile));
                                coveragePath = Path.Combine(coveragePath, "In");
                                
                                // TeamBuild will always use the unqualified name of the server in this path, however, the build
                                // server may be being referenced using a FQDN, so we'll just take the name upto the first "." character
                                // if one exists
                                //
                                // TODO: If the build server is specified by IP address, then this functionality will break.
                                if (TeamBuildServer.Contains("."))
                                {
                                    string unqualifiedBuildServer = TeamBuildServer.Substring(0, TeamBuildServer.IndexOf("."));
                                    coveragePath = Path.Combine(coveragePath, unqualifiedBuildServer);
                                }
                                else
                                {
                                    coveragePath = Path.Combine(coveragePath, TeamBuildServer);
                                }

                                if (File.Exists(Path.Combine(coveragePath, coverageFilename)))
                                {
                                    Log.Info("Adding MSTest Code Coverage result file(s) to build result");
                                    Log.Debug(string.Format("CoveragePath: {0}", coveragePath));

                                    string coverageXml = processCoverageFile(Path.Combine(coveragePath, coverageFilename));
                                    Log.Debug(string.Format("CoverageXml: {0}", coverageXml));

                                    result.AddTaskResult(new FileTaskResult(coverageXml));
                                }
                                else
                                {
                                    Log.Info(string.Format("No MSTest Code Coverage data file(s) found."));
                                }
                            }
                        }
                    }
                    else
                    {
                        Log.Info(string.Format("No MSTest result file(s) found."));
                    }
                }
            }
            catch (Exception ex)
            {
                result.Status = IntegrationStatus.Exception;
                result.ExceptionResult = new Exception(string.Format("Result of TeamBuild {0} in {1} on {2}: EXCEPTION - {3}",
                    this.TeamBuildType, this.TeamProject, this.TeamBuildServer, ex.ToString()));
            }
        }
        #endregion

        /// <summary>
        /// Processes a binary VSTS code coverage results file and converts into an XML
        /// representation, using ??? utility.
        /// </summary>
        private string processCoverageFile(string binaryCoverageFile)
        {
            Log.Debug(string.Format("Converting coverage file: {0}", binaryCoverageFile));
            string outDir = Path.GetFullPath(Path.Combine(Path.GetDirectoryName(binaryCoverageFile), @"..\..\Out"));
            string coverageXml = Path.Combine(Path.GetDirectoryName(binaryCoverageFile), coverageXmlFilename);
            Log.Debug(string.Format("Coverage file: {0}", coverageXml));

            // We need to convert the binary coverage file into a more useable format
            string p = string.Format("/infile:\"{0}\" /outfile:\"{1}\" /sympath:\"{2}\" /exepath:\"{2}\"",
                                        binaryCoverageFile,
                                        coverageXml,
                                        outDir);
            Log.Debug(string.Format("Running coverage conversion tool from {0}", Environment.CurrentDirectory));
            Log.Debug(string.Format("Running coverage conversion tool with params {0}", p));
            int ret = Utilities.ShellExecute(coverageConvertTool, p, Environment.CurrentDirectory);

            Log.Debug(string.Format("Coverage conversion tool exited with code: {0}", ret));

            return Path.GetFullPath(coverageXml);
        }

    }
}
